/**
 * @file wmdrv.c
 *
 */

/*********************
 *      INCLUDES
 *********************/
#include "wmdrv.h"

#if USE_WMDRV

// #define CONFIG_WMFB 1

#ifdef CONFIG_WMFB
#include <nxos.h>
#else
#include <wm/window.h>
#endif

/*********************
 *      DEFINES
 *********************/

/**********************
 *      TYPEDEFS
 **********************/

/**********************
 *  STATIC PROTOTYPES
 **********************/
#ifndef CONFIG_WMFB
static void keyboard_press(int kcode);
static void keyboard_release(int kcode);
static void mouse_pressed(int mx, int my);
static void mouse_released(void);
static void mouse_motion(int mx, int my);
#endif

/**********************
 *  STATIC VARIABLES
 **********************/
#ifdef CONFIG_WMFB

NX_PRIVATE NX_Solt fb_dev = NX_SOLT_INVALID_VALUE;
NX_PRIVATE NX_U32 *fb_buf;
NX_PRIVATE int fb_width;
NX_PRIVATE int fb_height;
#else
NX_Window * win;
NX_Surface * surface;
#endif

static lv_coord_t mouse_x;
static lv_coord_t mouse_y;
static lv_indev_state_t mouse_btn = LV_INDEV_STATE_REL;
static lv_key_t last_key;
static lv_indev_state_t last_key_state;

static int16_t wheel_diff = 0;
static lv_indev_state_t wheel_state = LV_INDEV_STATE_RELEASED;

/**********************
 *      MACROS
 **********************/

/**********************
 *   GLOBAL FUNCTIONS
 **********************/

#ifdef CONFIG_WMFB
static NX_Error FrameBufferInit(void)
{
    NX_Error err;

    /* open device */
    fb_dev = NX_DeviceOpen("fb0", 0);
    if (fb_dev == NX_SOLT_INVALID_VALUE)
    {
        NX_Printf("display: open video device failed!\n");
        return NX_ENORES;
    }

    NX_FramebufferInfo fbi;

    if (NX_DeviceControl(fb_dev, NX_FRAMEBUFFER_CMD_GETINFO, &fbi) != NX_EOK)
    {
        NX_Printf("display: get video info failed!\n");
        err = NX_EPERM;
        goto close_fb_dev;
    }

    NX_Printf("display: fbinfo: bpp:%d, scanline:%d, x:%d, y:%d, base:%p\n",
        fbi.bitsPerPixel, fbi.bytesPerScanLine, fbi.xResolution, fbi.yResolution, fbi.phyBasePtr);

    int bytes_per_pixel = fbi.bitsPerPixel / 8;
    NX_Size fb_len = bytes_per_pixel * fbi.yResolution * fbi.xResolution; 
    fb_buf = NX_DeviceMap(fb_dev, fb_len, NX_PROT_READ | NX_PROT_WRITE);
    if (fb_buf == NX_NULL) {
        NX_Printf("display: map video buffer failed!\n");
        err = NX_EPERM;
        goto close_fb_dev;
    }

    fb_width = fbi.xResolution;
    fb_height = fbi.yResolution;
#if 0
    fb_buf[1 * fb_width + 1] = 0xffff0000;
    fb_buf[400 * fb_width + 1] = 0xffff0000;
    fb_buf[799 * fb_width + 1] = 0xffff0000;
    
    fb_buf[1 * fb_width + 240] = 0xffff00ff;
    fb_buf[400 * fb_width + 240] = 0xffff00ff;
    fb_buf[799 * fb_width + 240] = 0xffff00ff;

    fb_buf[1 * fb_width + 479] = 0xffffff00;
    fb_buf[400 * fb_width + 479] = 0xffffff00;
    fb_buf[799 * fb_width + 479] = 0xffffff00;

    while (1)
    {
        /* code */
    }
#endif

    return NX_EOK;

close_fb_dev:
    NX_DeviceClose(fb_dev);
    return err;
}

int wmdrv_init(void)
{
    NX_Printf("init wm\n");
    return FrameBufferInit() == NX_EOK ? 0 : -1;
}
#else
int wmdrv_init(void)
{
    if (NX_WindowInit() != NX_EOK)
    {
        return -1;
    }

    NX_VideoInfo * info = NX_VideoGetInfo();
    if (!info)
    {
        NX_WindowExit();
        return -1;
    }

    win = NX_WindowCreate(0, 0, info->width, info->height, NX_ColorRGB(0xffffff), NX_WINDOW_FLAG_FULLSCREEN_DESKTOP);
    if (!win)
    {
      return -1;
    }
    surface = NX_WindowGetSurface(win);
    NX_WindowShow(win);
    return 0;
}
#endif


/*Set in lv_conf.h as `LV_TICK_CUSTOM_SYS_TIME_EXPR`*/
unsigned long wmdrv_tick_get(void)
{
    static NX_TimeVal start_ms = 0;
    if(start_ms == 0) {
        start_ms = NX_ClockGetMillisecond();
    }

    NX_TimeVal now_ms = NX_ClockGetMillisecond();

    unsigned long time_ms = now_ms - start_ms;

    return time_ms;
}

/**
 * Flush a buffer to the marked area
 * @param disp_drv pointer to driver where this function belongs
 * @param area an area where to copy `color_p`
 * @param color_p an array of pixels to copy to the `area` part of the screen
 */
void wmdrv_flush_cb(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p)
{
    lv_coord_t hres = disp_drv->rotated == 0 ? disp_drv->hor_res : disp_drv->ver_res;
    lv_coord_t vres = disp_drv->rotated == 0 ? disp_drv->ver_res : disp_drv->hor_res;


    /*Return if the area is out the screen*/
    if(area->x2 < 0 || area->y2 < 0 || area->x1 > hres - 1 || area->y1 > vres - 1) {

        lv_disp_flush_ready(disp_drv);
        return;
    }

    int32_t y;
    int32_t x;
    int32_t p;
    for(y = area->y1; y <= area->y2 && y < disp_drv->ver_res; y++) {
        p = (y * disp_drv->hor_res + area->x1);
        for(x = area->x1; x <= area->x2 && x < disp_drv->hor_res; x++) {

#ifdef CONFIG_WMFB
            *(NX_U32 *)&fb_buf[p] = lv_color_to32(*color_p);
            p++;
#else
            *(NX_U32 *)&surface->pixels[p] = lv_color_to32(*color_p);
            p++;
#endif
            color_p ++;
        }
    }

#ifndef CONFIG_WMFB
    /* flush wmdrv */
    NX_WindowUpdate(win);
#endif

    /*IMPORTANT! It must be called to tell the system the flush is ready*/
    lv_disp_flush_ready(disp_drv);
}

#ifdef CONFIG_WMFB
lv_coord_t wmdrv_get_width(void)
{
    return fb_width;
}

lv_coord_t wmdrv_get_height(void)
{
    return fb_height;
}
#else
lv_coord_t wmdrv_get_width(void)
{
    return surface->width;
}

lv_coord_t wmdrv_get_height(void)
{
    return surface->height;
}
#endif

void wmdrv_mouse_read_cb(lv_indev_drv_t * drv, lv_indev_data_t * data)
{
    data->point.x = mouse_x;
    data->point.y = mouse_y;
    data->state = mouse_btn;
}

/**
 * Get encoder (i.e. mouse wheel) ticks difference and pressed state
 * @param indev_drv pointer to the related input device driver
 * @param data store the read data here
 */
void wmdrv_mousewheel_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data)
{
    (void) indev_drv;      /*Unused*/

    data->state = wheel_state;
    data->enc_diff = wheel_diff;
    wheel_diff = 0;
}

void wmdrv_keyboard_read_cb(lv_indev_drv_t * drv, lv_indev_data_t * data)
{
    data->key = last_key;
    data->state = last_key_state;
}

#ifdef CONFIG_WMFB
/* has event return 0, or not return -1 */
int wmdrv_poll_event(void)
{
    return -1;
}
#else
/* has event return 0, or not return -1 */
int wmdrv_poll_event(void)
{
    NX_WindowEvent e;
    if (NX_WindowPollEvent(win, &e) == NX_EOK)
    {
        switch (e.type)
        {
        case NX_KEYDOWN_EVENT:
            keyboard_press(e.keydown_event.code);
            break;
        case NX_KEYUP_EVENT:
            keyboard_release(e.keyup_event.code);
            break;
        case NX_MOUSEBUTTONDOWN_EVENT:
            if (e.mouse_buttondown_event.code == NX_BTN_MOUSE_LEFT)
            {
                mouse_pressed(e.mouse_buttondown_event.x, e.mouse_buttondown_event.y);
            }
            else if (e.mouse_buttondown_event.code == NX_BTN_MOUSE_MIDDLE)
            {
                wheel_state = LV_INDEV_STATE_PRESSED;
            }
            break;
        case NX_MOUSEBUTTONUP_EVENT:
            if (e.mouse_buttonup_event.code == NX_BTN_MOUSE_LEFT)
            {
                mouse_released();
            }
            else if (e.mouse_buttonup_event.code == NX_BTN_MOUSE_MIDDLE)
            {
                wheel_state = LV_INDEV_STATE_RELEASED;
            }
            break;
        case NX_ABS_EVENT:
            mouse_motion(e.abs_event.x, e.abs_event.y);
            break;
        case NX_MOUSEWHEELUP_EVENT:
            wheel_state = -1;
            break;
        case NX_MOUSEWHEELDOWN_EVENT:
            wheel_state = 1;
            break;
        default:
            break;
        }
        return 0;
    }
    return -1;
}
#endif

/**********************
 *   STATIC FUNCTIONS
 **********************/

static void mouse_pressed(int mx, int my)
{
    mouse_btn = LV_INDEV_STATE_PR;
    mouse_x = mx;
    mouse_y = my;
}

static void mouse_released(void)
{
    mouse_btn = LV_INDEV_STATE_REL;
}

/*****************************************************************************/

static void mouse_motion(int mx, int my)
{
    mouse_x = mx;
    mouse_y = my;
}

#ifndef CONFIG_WMFB
static void keyboard_press(int kcode)
{
    uint32_t ascii_key = kcode;
    /*Remap some key to LV_KEY_... to manage groups*/
    switch(kcode) {
        case NX_KEY_RIGHT:
            ascii_key =  LV_KEY_RIGHT;
            break;

        case NX_KEY_LEFT:
            ascii_key =  LV_KEY_LEFT;
            break;

        case NX_KEY_UP:
            ascii_key =  LV_KEY_UP;
            break;

        case NX_KEY_DOWN:
            ascii_key =  LV_KEY_DOWN;
            break;

        case NX_KEY_ESCAPE:
            ascii_key =  LV_KEY_ESC;
            break;

        case NX_KEY_BACKSPACE:
            ascii_key =  LV_KEY_BACKSPACE;
            break;

        case NX_KEY_DELETE:
            ascii_key = LV_KEY_DEL;
            break;

        case NX_KEY_TAB:
            ascii_key = LV_KEY_NEXT;
            break;

        case NX_KEY_ENTER:
        case NX_KEY_KP_ENTER:
        case '\r':
            ascii_key = LV_KEY_ENTER;
            break;

        default:
            break;

    }

     last_key = ascii_key;
     last_key_state = LV_INDEV_STATE_PR;

}

static void keyboard_release(int kcode)
{
     last_key = 0;
     last_key_state = LV_INDEV_STATE_REL;
}
#endif


#endif /*USE_WMDRV*/

